package com.faner4cloud.yun.common.redis.lock;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.DecoratedRedisConnection;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.UUID;


@Slf4j
@Component
public class RedisLock implements IRedisLock {

    private static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";

    /**
     * 改为实例维度 避免父线程lock 子线程release
     */
    private static final String INSTANCE_ID = UUID.randomUUID().toString();

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 默认锁定5秒
     */
    @Override
    public boolean tryLock(String key) {
        return tryLock(key, 5000);
    }

    /**
     *
     * @param expire 锁定时间 单位 ms
     */
    @Override
    public boolean tryLock(String key, long expire) {
        return tryLock(key, expire, 1, -1);
    }

    @Override
    public boolean tryLock(String key, long expire, int retryTimes, long sleepMillis) {
        boolean result = setNxPx(key, expire);
        while ((!result) && --retryTimes > 0) {
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
            result = setNxPx(key, expire);
        }
        return result;
    }

    @Override
    public void releaseLock(String key) {
        redisTemplate.execute((RedisCallback<Long>) connection -> {
            Object nativeConnection = connection.getNativeConnection();
            // 集群模式
            if (isClusterConnection(connection)) {
                return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, Collections.singletonList(key), Collections.singletonList(INSTANCE_ID));
            } else {
                return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, Collections.singletonList(key), Collections.singletonList(INSTANCE_ID));
            }
        });
    }

    private boolean setNxPx(String key, long expire) {
        try {
            String result = (String) redisTemplate.execute((RedisCallback<String>) connection -> {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
				//return commands.set(key, INSTANCE_ID, "NX", "PX", expire)
				SetParams params = SetParams.setParams().nx().px(expire);
				return commands.set(key, INSTANCE_ID, params);
            });
            return !StrUtil.isEmpty(result);
        } catch (Exception e) {
            log.error(this.getClass().getName(), e);
        }
        return false;
    }

    /**
     * @see org.springframework.data.redis.cache.RedisCache
     */
    private static boolean isClusterConnection(RedisConnection connection) {

        while (connection instanceof DecoratedRedisConnection) {
            connection = ((DecoratedRedisConnection) connection).getDelegate();
        }

        return connection instanceof RedisClusterConnection;
    }

	public static void main(String[] args) {
//		String checkLockKey = String.format(Key, orderId);
//		if (RedisLock.tryLock(checkLockKey)) {
//			try {
				//....
//			} finally {
//				redisLock.releaseLock(checkLockKey);
//			}
//		}
	}

}
